/*********************
 * F I N D   G O A L *
 *********************/

% Attribute Value Already Known
findgoal(not Goal, NCF):-
	log(findgoal1(not Goal, NCF)),
	findgoal(Goal, CF),
	NCF is - CF,
	!.

findgoal(av(Attr, Val), CF):-
	log(findgoal2(av(Attr, Val), CF)),
	fact(av(Attr, Val), CF),
	!.

% Ask User for Attribute Value
findgoal(av(Attr, Val), CF):-
	log(findgoal3(av(Attr, Val), CF)),
	not fact(av(Attr, _), _),
	%log(not fact(av(Attr, _), _)),
	askable(Attr, Prompt),
	%log(askable(Attr, Prompt)),
	query_user(Attr, Prompt),
	%log('query_user'),
	!,
	findgoal(av(Attr, Val), CF).
findgoal(X; Y, CF):-
	findgoal(X, CF1),
	findgoal(Y, CF2),
	max(CF1, CF2, CF),
	!.
findgoal((X, Y), CF):-
	findgoal(X, CF1),
	findgoal(Y, CF2),
	min(CF1, CF2, CF),
	!.

query_user(Attr, Prompt):-
	ask(Attr, Prompt) -> 
	(
		%log(default(av(Attr, Val), CF)),
		(
			default(av(Attr, Val), CF); 
			(
				%log(unknown(av(Attr, Val), CF)),
				Val = unknown,
				CF = 0
			)
		),
		%log('assert_default'),
		asserta(fact(av(Attr, Val), CF))
	);
	(
		%log(read(Attr)),
		write(Prompt),
		write(' '),
		read(Val),
		%log('read!!!'),
		read(CF),
		asserta(fact(av(Attr, Val), CF))
	).

% Deduce Attribute Value from Rules
findgoal(Goal, CurCF):-
	findgoal2(Goal, CurCF).

findgoal2(Goal, CurCF):-
	rule(N, lhs(IfList), rhs(Goal, CF)),
	log(rule(N, lhs(IfList), rhs(Goal, CF))),
	prove(IfList, Tally),
	adjust(CF, Tally, NewCF),
	update(Goal, NewCF, CurCF),
	CurCF == 1, !.
	
findgoal2(Goal, CF):-
	fact(Goal, CF).

prove(IfList, Tally):-
	prove2(IfList, 1, Tally).

prove2([], Tally, Tally).
prove2([H|T], CurTal, Tally):-
	findgoal(H, CF),
	(
		CF == -1 -> fail;
		min(CurTal, CF, Tal),
		Tal >= -0.2,
		prove2(T, Tal, Tally)
	).

adjust(CF1, CF2, CF):-
	CF is CF1 * CF2.

update(Goal, NewCF, CF) :-
	fact(Goal, OldCF),
	combine(NewCF, OldCF, CF),
	retract(fact(Goal, OldCF)),
	asserta(fact(Goal, CF)),
	!.
update(Goal, CF, CF):-
	asserta(fact(Goal, CF)).

combine(CF1, CF2, CF):-
	CF1 >= 0,
	CF2 >= 0,
	CF is CF1 + CF2*(1 - CF1).
combine(CF1, CF2, CF):-
	CF1 < 0,
	CF2 < 0,
	CF is CF1 + CF2*(1 + CF1).
combine(CF1, CF2, CF):-
	(CF1 < 0; CF2 < 0),
	(CF1 > 0; CF2 > 0),
	abs_minimum(CF1, CF2, MCF),
	CF is (CF1 + CF2)/(1 - MCF).

/***********************
 * U L T I L I T I E S *
 ***********************/

abs_minimum(A, B, X):-
	absolute(A, AA),
	absolute(B, BB),
	min(AA, BB, X).

absolute(X, X):-
	X >= 0.
absolute(X, -X):-
	X < 0.

min(X, Y, X):-
	X =< Y,
	!.
min(X, Y, Y):-
	Y =< X.
	
max(X, Y, X):-
	X >= Y,
	!.
max(X, Y, Y):-
	Y >= X.

% Merge Sort
mergesort([], []).
mergesort([A], [A]).
mergesort([A,B|R], S):-
	split([A,B|R], L1, L2),
	mergesort(L1, S1),
	mergesort(L2, S2),
	merge(S1, S2, S).

split([], [], []).
split([A], [A], []).
split([A,B|R], [A|Ra], [B|Rb]):-
	split(R, Ra, Rb).

merge(A, [], A).
merge([], B, B).
merge([(A, CFA)|Ra], [(B, CFB)|Rb], [(A, CFA)|M]):-
	CFA >= CFB, merge(Ra, [(B, CFB)|Rb], M).
merge([(A, CFA)|Ra], [(B, CFB)|Rb], [(B, CFB)|M]):-
	CFA < CFB, merge([(A, CFA)|Ra], Rb, M).

% Roulette Wheel Selection
sum([], 0).
sum([(X, CFX)|L], Sum):-
	sum(L, SL), Sum is CFX + SL.

roulette_wheel(L, M):-
	sum(L, S), random(Rand), roulette_wheel2(L, S, 0, Rand, M).
roulette_wheel2([(X, CFX)|T], Sum, Temp, Rand, M):-
	Temp2 is (Temp + CFX/Sum),
	(
		Temp2 > Rand -> M = (X, CFX);
		roulette_wheel2(T, Sum, Temp2, Rand, M)
	).
